﻿#include "QCPToolTip.h"

QCPToolTip::QCPToolTip(QCustomPlot *parentPlot)
	: QCPAbstractItem(parentPlot),
	position(createPosition(QLatin1String("position"))),
	mPlotReplot(false),
	mTextAlignment(Qt::AlignLeft | Qt::AlignVCenter),
	mRadius(6, 6),
	mSizeMode(Qt::AbsoluteSize),
	mHighlightGraph(nullptr)
{
	position->setType(QCPItemPosition::ptAbsolute);
	setSelectable(false);
	setLayer("overlay");

	setBorderPen(Qt::NoPen);
	setBrush(QColor(87, 98, 93, 180));
	setTextColor(Qt::white);
	setOffset(20, 20);
	setPadding(QMargins(6, 6, 6, 6));
	connect(mParentPlot, SIGNAL(mouseMove(QMouseEvent *)), this, SLOT(handleTriggerEvent(QMouseEvent *)));
}
void QCPToolTip::setText(const QString &text)
{
	mText = text;
};
void QCPToolTip::setFont(const QFont &font)
{
	mFont = font;
};
void QCPToolTip::setTextColor(const QColor &color)
{
	mTextColor = color;
};
void QCPToolTip::setBorderPen(const QPen &pen)
{
	mBorderPen = pen;
};
void QCPToolTip::setBrush(const QBrush &brush)
{
	mBrush = brush;
};
void QCPToolTip::setRadius(double xRadius, double yRadius, Qt::SizeMode mode /*= Qt::AbsoluteSize*/)
{
	mRadius = QPointF(xRadius, yRadius);
	mSizeMode = mode;
};
void QCPToolTip::setOffset(double xOffset, double yOffset)
{
	mOffset = QPointF(xOffset, yOffset);
};
void QCPToolTip::setPadding(const QMargins &paddings)
{
	mPadding = paddings;
};

void QCPToolTip::handleTriggerEvent(QMouseEvent *event)
{
	updatePosition(event->pos(), true);   // true 表示需要单独刷新，将调用update函数
}

void QCPToolTip::update()
{
	mPlotReplot = false;    // 表明单独刷新
	layer()->replot();
	mPlotReplot = true;    // 单独刷新完毕
}

// 不需要鼠标点击测试，因为ToolTip是跟随鼠标的，鼠标点击不到
double QCPToolTip::selectTest(const QPointF &pos, bool onlySelectable, QVariant *details) const
{
	Q_UNUSED(pos)
		Q_UNUSED(onlySelectable)
		Q_UNUSED(details)
		return -1;
}
int QCPToolTip::pickClosest(double target, const QVector<double> &vector)
{
	if (vector.size() < 2)
		return 0;

	// 查找第一个大于或等于target的位置
	auto it = std::lower_bound(vector.constBegin(), vector.constEnd(), target);

	if (it == vector.constEnd()) return vector.size() - 1;
	else if (it == vector.constBegin()) return 0;
	else return target - *(it - 1) < *it - target ? (it - vector.constBegin() - 1) : (it - vector.constBegin());
}

void QCPToolTip::updatePosition(const QPointF &newPos, bool replot)
{
	mHighlightGraph = nullptr;
	double tolerance = mParentPlot->selectionTolerance();

	for (int i = mParentPlot->graphCount() - 1; i >= 0; --i)
	{
		QCPGraph *graph = mParentPlot->graph(i);
		// graph不可见或者scatter style 为空的时候，不显示ToolTip
		if (!graph->realVisibility() || graph->scatterStyle().isNone())
			continue;

		double limitDistance = tolerance;   // limitDistance 用于选择的范围
		double penWidth = graph->pen().widthF();
		QCPScatterStyle scatterStyle = graph->scatterStyle();

		limitDistance = qMax(scatterStyle.size(), tolerance);
		penWidth = scatterStyle.isPenDefined() ? scatterStyle.pen().widthF() : penWidth;

		// details会返回最接近的一个数据点，selectTest是不精确的，所以后面还要判断
		QVariant details;
		double currentDistance = graph->selectTest(newPos, false, &details);

		QCPDataSelection selection = details.value<QCPDataSelection>();
		if (currentDistance >= 0 && currentDistance < limitDistance + penWidth && !selection.isEmpty())
		{
			// 取出当前key和value值，并且转换为像素位置
			double key = graph->dataMainKey(selection.dataRange().begin());
			double value = graph->dataMainValue(selection.dataRange().begin());
			QPointF pos = graph->coordsToPixels(key, value);

			QRectF rect(pos.x() - limitDistance * 0.5, pos.y() - limitDistance * 0.5, limitDistance, limitDistance);
			rect = rect.adjusted(-penWidth, -penWidth, penWidth, penWidth);

			// 判断鼠标位置是否在数据点上
			if (rect.contains(newPos))
			{
				mHighlightGraph = graph;
				mGraphDataPos = pos;

				mParentPlot->setCursor(Qt::PointingHandCursor);
				position->setPixelPosition(newPos);  // 更新位置
				setVisible(true);

				if (replot) {
					update();
				}
				break;
			}
		}
	}

	if (!mHighlightGraph && visible())
	{
		mParentPlot->setCursor(Qt::ArrowCursor);
		setVisible(false);
		if (replot) {
			update();
		}
	}
}
void QCPToolTip::draw(QCPPainter *painter)
{
	if (mPlotReplot) {  // 当前是由QCustomPlot的replot函数刷新的，所以要更新位置
		updatePosition(position->pixelPosition(), false);  // false表明不刷新
		if (!visible()) {
			// 由于位置更新之后，ToolTip可能会隐藏掉了，所以此处直接返回
			return;
		}
	}

	drawGraphScatterPlot(painter, mHighlightGraph, mGraphDataPos);

	QPointF pos = position->pixelPosition() + mOffset;
	painter->translate(pos);  // 移动painter的绘制原点位置

	QFontMetrics fontMetrics(mFont);
	QRect textRect = fontMetrics.boundingRect(0, 0, 0, 0, Qt::TextDontClip | mTextAlignment, mText);
	textRect.moveTopLeft(QPoint(mPadding.left(), mPadding.top()));

	QRect textBoxRect = textRect.adjusted(-mPadding.left(), -mPadding.top(), mPadding.right(), mPadding.bottom());
	textBoxRect.moveTopLeft(QPoint());

	// 限制ToolTip不超过QCustomPlot的范围
	if (pos.x() + textBoxRect.width() >= mParentPlot->viewport().right())
		painter->translate(-mOffset.x() * 2 - textBoxRect.width(), 0);
	if (pos.y() + textBoxRect.height() * 2 >= mParentPlot->viewport().bottom())
		painter->translate(0, -mOffset.y() * 2 - textBoxRect.height());

	// 绘制背景和边框
	if ((mBrush != Qt::NoBrush && mBrush.color().alpha() != 0) ||
		(mBorderPen != Qt::NoPen && mBorderPen.color().alpha() != 0))
	{
		double clipPad = mBorderPen.widthF();
		QRect boundingRect = textBoxRect.adjusted(-clipPad, -clipPad, clipPad, clipPad);

		painter->setPen(mBorderPen);
		painter->setBrush(mBrush);
		painter->drawRoundedRect(boundingRect, mRadius.x(), mRadius.y(), mSizeMode);
	}

	// 绘制文字
	painter->setFont(mFont);
	painter->setPen(mTextColor);
	painter->setBrush(Qt::NoBrush);
	painter->drawText(textRect, Qt::TextDontClip | mTextAlignment, mText);
}

void QCPToolTip::drawGraphScatterPlot(QCPPainter *painter, QCPGraph *graph, const QPointF &pos)
{
	if (!graph) return;

	QCPScatterStyle style = graph->scatterStyle();
	if (style.isNone()) return;

	if (graph->selectionDecorator())  // 如果有select decorator，则使用修饰器的风格
		style = graph->selectionDecorator()->getFinalScatterStyle(style);

	style.applyTo(painter, graph->pen());
	style.setSize(style.size() * 1.2); // 放大一点
	style.drawShape(painter, pos);
}
